package com.jintian.smart.kernel.orm.service;

import com.jintian.smart.kernel.common.util.StringUtil;
import com.jintian.smart.kernel.core.expr.IExprCalcContext;
import com.jintian.smart.kernel.core.expr.IExpressionCalc;
import com.jintian.smart.kernel.core.exception.CreateEntityException;
import com.jintian.smart.kernel.orm.mapper.IBaseExtMapper;
import org.beetl.core.GroupTemplate;
import org.beetl.core.resource.StringTemplateResourceLoader;
import org.beetl.sql.annotation.builder.FillStrategy;
import org.beetl.sql.annotation.builder.UpdateTime;
import org.beetl.sql.annotation.builder.UpdateValue;
import org.beetl.sql.annotation.entity.AssignID;
import org.beetl.sql.annotation.entity.Column;
import org.beetl.sql.annotation.entity.Version;
import org.beetl.sql.clazz.ClassDesc;
import org.beetl.sql.core.SQLManager;
import org.beetl.sql.core.engine.UpdateTimeExpressionFunction;
import org.beetl.sql.core.engine.template.BeetlTemplateEngine;
import org.beetl.sql.core.page.PageRequest;
import org.beetl.sql.core.page.PageResult;
import org.beetl.sql.core.query.LambdaQuery;
import org.beetl.sql.ext.UUIDAutoGen22;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.FieldCallback;
import org.springframework.util.ReflectionUtils.FieldFilter;

import java.io.Serializable;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

public interface IService<T> {

    /**
     * 获取 entity 的 class
     *
     * @return {@link Class<T>}
     */
    @SuppressWarnings("unchecked")
    default Class<T> getEntityClass() {
        return (Class<T>) this.getEntityMapper().getTargetEntity();
    }

    /**
     * 通用插入，插入一个实体对象到数据库，所以字段将参与操作，除非你使用ColumnIgnore注解 SqlResource
     *
     * @param entity 实体对象
     */
    @Transactional
    default Integer save(T entity) {
        return getEntityMapper().insert(entity);
    }

    /**
     * 插入实体到数据库，对于null值不做处理
     *
     * @param entity
     */
    @Transactional
    default void saveTemplate(T entity) {
        getEntityMapper().insertTemplate(entity);
    }

    /**
     * 插入（批量）
     *
     * @param entityList 实体对象集合
     */
    @Transactional
    default void saveBatch(List<T> entityList) {
        getEntityMapper().insertBatch(entityList);
    }

    /**
     * 插入（批量）
     *
     * @param entityList 实体对象集合
     * @param batchSize  插入批次数量
     */
    @Transactional
    default void saveBatch(List<T> entityList, int batchSize) {
        getEntityMapper().insertBatch(entityList, batchSize);
    }

    /**
     * 批量修改插入
     *
     * @param entityList 实体对象集合
     */
    @Transactional
    default boolean[] saveOrUpdateBatch(List<T> entityList) {
        boolean[] ret = new boolean[entityList.size()];
        int idx = 0;
        for (T entity : entityList) {
            ret[idx++] = getEntityMapper().upsert(entity);
        }
        return ret;
    }

    /**
     * 根据 ID 删除，支持逻辑删除
     *
     * @param id 主键ID
     */
    @Transactional
    default Integer deleteById(Serializable id) {
        return getEntityMapper().deleteById(id);
    }

    /**
     * 根据 columnMap 条件，删除记录,支持逻辑删除
     *
     * @param columnMap 表字段 map 对象
     */
    @Transactional
    default int deleteByMap(Map<String, Object> columnMap) {
        Assert.notEmpty(columnMap, "error: columnMap must not be empty");
        LambdaQuery<T> query = this.query();
        for (Entry<String, Object> e : columnMap.entrySet()) {
            query.andEq(e.getKey(), e.getValue());
        }
        ClassDesc classDesc = this.getSQLManager().getClassDesc(this.getEntityClass());
        if (classDesc.getClassAnnotation().getLogicDeleteAttrName() == null) {
            return query.delete();
        } else {
            Map<String, Object> update = new HashMap<String, Object>();
            for (String id : classDesc.getIdAttrs()) {
                update.put(id, columnMap.get(id));
            }
            update.put(classDesc.getClassAnnotation().getLogicDeleteAttrName(), classDesc.getClassAnnotation().getLogicDeleteAttrValue());
            return query.update(update);
        }

    }

    /**
     * 链式查询
     */
    default LambdaQuery<T> query() {
        LambdaQuery<T> query = this.getSQLManager().lambdaQuery(this.getEntityClass());
        return query;
    }

    /**
     * 删除（根据ID 批量删除），支持逻辑删除
     *
     * @param idList 主键ID列表
     */
    @Transactional
    default int[] deleteByIds(List<?> idList) {
        if (CollectionUtils.isEmpty(idList)) {
            return new int[]{0};
        }
        int[] ret = new int[idList.size()];
        int idx = 0;
        for (Object key : idList) {
            ret[idx++] = getEntityMapper().deleteById(key);
        }
        return ret;
    }

    /**
     * 根据主键更新对象，所以属性都参与更新。也可以使用主键ColumnIgnore来控制更新的时候忽略此字段
     *
     * @param entity
     * @return
     */
    @Transactional
    default Integer updateById(T entity) {
        return getEntityMapper().updateById(entity);
    }

    /**
     * 根据主键更新对象，只有不为null的属性参与更新
     *
     * @return
     */
    @Transactional
    default Integer updateTemplateById(T template) {
        return getEntityMapper().updateTemplateById(template);
    }

    /**
     * 只更新Map包含的属性，支持空值更新。必须包含主键属性和版本属性(如果存在)
     *
     * @param template
     * @return
     */
    @Transactional
    default boolean updateTemplate2ById(Map<String, Object> template) {
        return getEntityMapper().updateTemplate2ById(template);
    }

    /**
     * 根据ID 批量更新
     *
     * @param entityList 实体对象集合
     */
    @Transactional
    default int[] updateBatchById(List<T> entityList) {
        return this.getSQLManager().updateByIdBatch(entityList);
    }

    /**
     * 根据ID 批量更新
     *
     * @param entityList 实体对象集合
     * @param batchSize  更新批次数量
     */
    @Transactional
    default int[] updateBatchById(List<T> entityList, int batchSize) {
        return this.getSQLManager().updateByIdBatch(entityList, batchSize);
    }

    /**
     * 存在更新记录，否插入一条记录
     *
     * @param entity 实体对象
     */
    @Transactional
    default boolean saveOrUpdate(T entity) {
        return getEntityMapper().upsert(entity);
    }

    /**
     * 存在更新记录，否插入一条记录
     *
     * @param entity 实体对象
     */
    @Transactional
    default boolean saveOrUpdateTemplate(T entity) {
        return getEntityMapper().upsertByTemplate(entity);
    }

    /**
     * 根据 ID 查询
     *
     * @param id 主键ID
     */
    default T getById(Serializable id) {
        return getEntityMapper().single(id);
    }

    /**
     * 查询（根据ID 批量查询）
     *
     * @param idList 主键ID列表
     */
    default List<T> listByIds(List<?> idList) {
        return getEntityMapper().selectByIds(idList);
    }

    /**
     * 查询（根据 columnMap 条件）
     *
     * @param columnMap 表字段 map 对象
     */
    default List<T> listByMap(Map<String, Object> columnMap) {
        Assert.notEmpty(columnMap, "error: columnMap must not be empty");
        LambdaQuery<T> query = this.query();
        for (Entry<String, Object> e : columnMap.entrySet()) {
            query.andEq(e.getKey(), e.getValue());
        }
        return query.select();
    }

    /**
     * 查询总记录数
     */
    default long count() {
        return getEntityMapper().allCount();
    }

    /**
     * 查询所有
     */
    default List<T> list() {
        return getEntityMapper().all();
    }

    /**
     * 模板翻页查询
     */
    @SuppressWarnings("unchecked")
    default PageResult<T> templatePage(T template, PageRequest<T> request) {
        return (PageResult<T>) getEntityMapper().templatePageQuery(template, request);
    }

    /**
     * 简单分页查询
     *
     * @param request
     * @return
     */
    default PageResult<T> simpleQuery(PageRequest<T> request) {
        return (PageResult<T>) getEntityMapper().simpleQuery(request);
    }

    /**
     * 清除逻辑删除的数据，可以是Map和JavaBean，必须包含主键
     *
     * @param obj
     * @return
     */
    default boolean clearLogicDeleteById(Object obj) {
        ClassDesc classDesc = this.getSQLManager().getClassDesc(this.getEntityClass());
        if (classDesc.getClassAnnotation().getLogicDeleteAttrName() == null) {
            throw new UnsupportedOperationException("不支持clearLogicDeleteById，未注解LogicDelete字段");
        }
        throw new UnsupportedOperationException("removeById");
    }

    /**
     * 清除逻辑删除的数据，可以是List<Map|JavaBean|单一主键>必须包含主键
     *
     * @param objs
     * @return
     */
    default int[] clearLogicDeleteByIds(List<?> objs) {
        ClassDesc classDesc = this.getSQLManager().getClassDesc(this.getEntityClass());
        if (classDesc.getClassAnnotation().getLogicDeleteAttrName() == null) {
            throw new UnsupportedOperationException("不支持clearLogicDeleteByIds，未注解LogicDelete字段");
        }
        throw new UnsupportedOperationException("removeByIds");
    }

    /**
     * 清除逻辑删除的数据
     *
     * @return
     */
    default int clearLogicDeleteAll() {
        ClassDesc classDesc = this.getSQLManager().getClassDesc(this.getEntityClass());
        if (classDesc.getClassAnnotation().getLogicDeleteAttrName() == null) {
            throw new UnsupportedOperationException("不支持clearLogicDelete，未注解LogicDelete字段");
        }
        int r = this.query().andEq(classDesc.getClassAnnotation().getLogicDeleteAttrName(), classDesc.getClassAnnotation().getLogicDeleteAttrValue()).delete();
        return r;
    }

    /**
     * 创建实例
     *
     * @return
     */
    default T create() {
        return this.create(null);
    }

    default IExprCalcContext getExprCalcContext() {
        return null;
    }

    /**
     * 创建实例，如果默认值表达式包含宿主参数，需传入宿主参数值
     *
     * @return
     */
    default T create(Map<String, Object> params) {
        return IService.create(this.getSQLManager(), this.getEntityClass(), this.getExpressionCalc(), this.getExprCalcContext(), params);
    }

    /**
     * 获取对应 entity 的 EntityMapper
     *
     * @return BaseMapper
     */
    IBaseExtMapper<T> getEntityMapper();

    default SQLManager getSQLManager() {
        return this.getEntityMapper().getSQLManager();
    }

    /**
     * 表达式计算器
     *
     * @return
     */
    IExpressionCalc getExpressionCalc();

    static final FieldFilter createFieldFilter = new FieldFilter() {
        @Override
        public boolean matches(Field field) {
            Column column = field.getAnnotation(Column.class);
            AssignID assignID = field.getAnnotation(AssignID.class);
            Version version = field.getAnnotation(Version.class);
            UpdateValue updateValue = field.getAnnotation(UpdateValue.class);
            UpdateTime updateTime = field.getAnnotation(UpdateTime.class);
            return column != null && StringUtil.isNotEmpty(column.defaultValueExpr()) || assignID != null && StringUtil.isNotEmpty(assignID.value()) || version != null || updateValue != null || updateTime != null;
        }
    };

    static <T> T create(SQLManager sqlManager, Class<T> entityClass, IExpressionCalc IExpressionCalc, IExprCalcContext context, Map<String, Object> params) {
        try {
            T instance = entityClass.newInstance();
            final Map<String, Object> variants = params != null ? params : new HashMap<>();
            if (context != null) {
                variants.putAll(context.getDefaultContextVariables());
            }
            variants.put("_root", instance);

            Map<Field, UpdateValue> updateValues = new HashMap<>();
            ReflectionUtils.doWithFields(entityClass, new FieldCallback() {
                @Override
                public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
                    field.setAccessible(true);
                    Column column = field.getAnnotation(Column.class);
                    AssignID assignID = field.getAnnotation(AssignID.class);
                    Version version = field.getAnnotation(Version.class);
                    UpdateValue updateValue = field.getAnnotation(UpdateValue.class);
                    UpdateTime updateTime = field.getAnnotation(UpdateTime.class);
                    if (column != null && StringUtil.isNotEmpty(column.defaultValueExpr())) {
                        String expr = column.defaultValueExpr();
                        field.set(instance, IExpressionCalc.calculate(expr, variants));
                    } else if (assignID != null && StringUtil.isNotEmpty(assignID.value())) {
                        String algorithm = assignID.value();
                        String param = assignID.param();
                        Object value = sqlManager.getAssignIdByIdAutoGen(algorithm, param, entityClass);
                        field.set(instance, value);
                    } else if (version != null) {
                        if (field.getType().equals(Long.class)) {
                            field.set(instance, Long.valueOf(version.value()));
                        } else if (field.getType().equals(Integer.class)) {
                            field.set(instance, version.value());
                        } else if (field.getType().isAssignableFrom(Date.class)) {
                            field.set(instance, new Date(System.currentTimeMillis()));
                        } else if (field.getType().isAssignableFrom(String.class)) {
                            field.set(instance, new UUIDAutoGen22().nextID(null));
                        }
                    } else if (updateTime != null && !FillStrategy.UPDATE.equals(updateTime.value())) {
                        Object value = new UpdateTimeExpressionFunction().call(new Object[]{field.getType().getName()}, null);
                        field.set(instance, value);
                    }
                    if (updateValue != null && !FillStrategy.UPDATE.equals(updateValue.strategy())) {
                        updateValues.put(field, updateValue);
                    }
                }
            }, createFieldFilter);
            for (Field field : updateValues.keySet()) {
                Object value = IService.calcUpdateValue(sqlManager, updateValues.get(field).value(), instance);
                field.set(instance, value);
            }
            return instance;
        } catch (Exception e) {
            throw new CreateEntityException(e);
        }
    }

    static Object calcUpdateValue(SQLManager sqlManager, String expr, Object bean) {
        StringTemplateResourceLoader resourceLoader = new StringTemplateResourceLoader() {
            private static final long serialVersionUID = 1L;

            @Override
            public boolean isCacheScript() {
                return false;
            }
        };
        GroupTemplate groupTemplate = ((BeetlTemplateEngine) sqlManager.getSqlTemplateEngine()).getBeetl().getGroupTemplate();
        Map<String, Object> paras = new HashMap<>();
        paras.put("_root", bean);
        @SuppressWarnings("unchecked") Map<String, Object> ret = groupTemplate.runScript("return " + expr + ";", paras, new StringWriter(), resourceLoader);
        return ret.get("return");
    }
}
